package com.zsc.edu.gateway.framework.security;

import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.util.AntPathMatcher;

import javax.sql.DataSource;

/**
 * @author harry_yao
 */
@AllArgsConstructor
@EnableMethodSecurity
@Configuration
public class SpringSecurityConfig {

    private final UserDetailsService userDetailsService;
    private final CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
    private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
    private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
    private final CustomAccessDeniedHandler customAccessDeniedHandler;
    private final SessionRegistry sessionRegistry;
    private final SecurityBeanConfig securityBeanConfig;
    private final CustomSessionInformationExpiredStrategy customSessionInformationExpiredStrategy;

    @Resource
    private final DataSource dataSource;

//    @Bean
//    public BCryptPasswordEncoder bCryptPasswordEncoder() {
//        return new BCryptPasswordEncoder();
//    };

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;

    }

    @Bean
    AuthenticationManager authenticationManager() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(securityBeanConfig.passwordEncoder());
        return new ProviderManager(daoAuthenticationProvider);
    }

    @Bean
    public JsonAuthenticationFilter jsonAuthenticationFilter() throws Exception {
        JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
        filter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
        filter.setFilterProcessesUrl("/api/rest/user/login");
        filter.setAuthenticationManager(authenticationManager());
        // 将登录后的请求信息保存到Session中，不然会报null
        filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
        return filter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        return http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(HttpMethod.GET, "/api/rest/user/menu","/api/rest/user/register","/api/rest/user/send-email").permitAll()
                        .requestMatchers(HttpMethod.POST, "/api/rest/user/login","/api/rest/user/register").permitAll()
                        .requestMatchers("/api/rest/user/me").permitAll()
                        .requestMatchers("/api/rest/ws/**").permitAll()
                        .requestMatchers("/api/**").authenticated()
                )
                        // 不用注解，直接通过判断路径实现动态访问权限
//                        .requestMatchers("/api/**").access((authentication, object) -> {
//                            //表示请求的 URL 地址和数据库的地址是否匹配上了
//                            boolean isMatch = false;
//                            //获取当前请求的 URL 地址
//                            String requestURI = object.getRequest().getRequestURI();
//                            List<MenuWithRoleVO> menuWithRole = menuService.getMenuWithRole();
//                            for (MenuWithRoleVO m : menuWithRole) {
//                                AntPathMatcher antPathMatcher = new AntPathMatcher();
//                                if (antPathMatcher.match(m.getUrl(), requestURI)) {
//                                    isMatch = true;
//                                    //说明找到了请求的地址了
//                                    //这就是当前请求需要的角色
//                                    List<Role> roles = m.getRoles();
//                                    //获取当前登录用户的角色
//                                    Collection<? extends GrantedAuthority> authorities = authentication.get().getAuthorities();
//                                    for (GrantedAuthority authority : authorities) {
//                                        for (Role role : roles) {
//                                            if (authority.getAuthority().equals(role.getName())) {
//                                                //说明当前登录用户具备当前请求所需要的角色
//                                                return new AuthorizationDecision(true);
//                                            }
//                                        }
//                                    }
//                                }
//                            }
//                            if (!isMatch) {
//                                //说明请求的 URL 地址和数据库的地址没有匹配上，对于这种请求，统一只要登录就能访问
//                                if (authentication.get() instanceof AnonymousAuthenticationToken) {
//                                    return new AuthorizationDecision(false);
//                                } else {
//                                    //说明用户已经认证了
//                                    return new AuthorizationDecision(true);
//                                }
//                            }
//                            return new AuthorizationDecision(false);
//                        }))
                .addFilterAt(jsonAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .formLogin(form -> form
                    .loginPage("/user/login")
                    .loginProcessingUrl("/api/rest/user/login")
                    .successHandler(customAuthenticationSuccessHandler)
                    .failureHandler(customAuthenticationFailureHandler))
                .logout(logout -> logout
                        .logoutUrl("/api/user/logout")
                        .logoutSuccessHandler((request, response, authentication) -> {}))
                // 添加自定义未授权和未登录结果返回
                .exceptionHandling(exception -> exception
                        .authenticationEntryPoint(customAuthenticationEntryPoint)
                        .accessDeniedHandler(customAccessDeniedHandler)
                )
                .rememberMe(rememberMe -> rememberMe
                        .userDetailsService(userDetailsService)
                        .tokenRepository(persistentTokenRepository()))
                .csrf(csrf -> csrf.ignoringRequestMatchers("/api/internal/**", "/api/rest/user/logout", "/api/rest/user/register", "/api/rest/ws/**"))
                .sessionManagement(session -> session
                        .maximumSessions(3)
                        .sessionRegistry(sessionRegistry)
                        .expiredSessionStrategy(customSessionInformationExpiredStrategy))
                .build();

    }
}
